// Το παρόν περιέχει δομές και μεθόδους που αφορούν στο σκηνικό τόσο του server
// όσο και των clients. Το σκηνικό αποτελείται κυρίως από τρεις βασικές λίστες,
// τη λίστα των τραπεζιών ("trapezi"), τη λίστα των ενεργών παικτών ("sinedria")
// και τη γενική λίστα των παικτών ("pektis"). Υπάρχει, επίσης, το array της
// συζήτησης που εξελίσσεται στο καφενείο ("sizitisi"), ενώ στον server υπάρχει
// και το tranasction log, δηλαδή ένα array μεταβολών. Στους clients το σκηνικό
// αντανακλά στο DOM μέσω εξειδικευμένων μεθόδων.

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Το global object "Skiniko" είναι singleton το οποίο απεικονίζει το σκηνικό της
// μηχανής στην οποία ορίζεται· στον server απεικονίζει το σκηνικό του server, ενώ
// στους clients απεικονίζει το σκηνικό των επιμέρους clients. Τα βασικά συστατικά
// τού σκηνικού είναι η λίστα των τραπεζιών, η λίστα των συνεδριών και η λίστα των
// παικτών. Ορίζονται επίσης και μέθοδοι που σχετίζονται με τις λίστες αυτές.

Skiniko = {
	// Η λίστα "trapezi" περιέχει τα τραπέζια δεικτοδοτημένα με τον
	// κωδικό τραπεζιού.

	trapezi: {},

	// Η μέθοδος "isTrapezi" δέχεται έναν κωδικό τραπεζιού και επιστρέφει
	// το ίδιο το τραπέζι εφόσον είναι ήδη ενταγμένο στο σκηνικό, αλλιώς
	// επιστρέφει undefined.

	isTrapezi: function(kodikos) {
		return this.trapezi[kodikos];
	},

	// Η μέθοδος "oxiTrapezi" δέχεται έναν κωδικό τραπεζιού και επιστρέφει
	// true εφόσον το τραπέζι ΔΕΝ είναι ενταγμένο στο σκηνικό, αλλιώς
	// επιστρέφει false.

	oxiTrapezi: function(kodikos) {
		return !this.isTrapezi(kodikos);
	},

	// Η μέθοδος "fetchTrapezi" δέχεται έναν κωδικό τραπεζιού και επιστρέφει
	// το ίδιο το τραπέζι το οποίο ΠΡΕΠΕΙ να είναι ενταγμένο στο σκηνικό.

	fetchTrapezi: function(kodikos) {
		var trapezi = Skiniko.isTrapezi(kodikos);
		if (!trapezi) globals.fatal(kodikos + ': το τραπέζι δεν βρέθηκε στο σκηνικό');
		return trapezi;
	},

	// Η μέθοδος "trapeziWalk" διατρέχει όλα τα τραπέζια του σκηνικού και
	// για κάθε τραπέζι καλεί callback function με παραμέτρους τον κωδικό
	// του τραπεζιού και το ίδιο το τραπέζι. Είτε δοθεί είτε δεν δοθεί
	// callback function, επιστρέφεται το πλήθος των τραπεζιών που είναι
	// ενταγμένα στο σκηνικό.

	trapeziWalk: function(callback) {
		return globals.walk(this.trapezi, callback);
	},

	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

	// Η λίστα "sinedria" περιέχει τους ενεργούς παίκτες δεικτοδοτημένους
	// με τα login names τους.

	sinedria: {},

	// Η μέθοδος "isSinedria" δέχεται το login name ενός παίκτη και εφόσον
	// ο παίκτης είναι ενεργός επιστρέφει την αντίστοιχη συνεδρία, αλλιώς
	// επιστρέφει undefined.

	isSinedria: function(login) {
		return this.sinedria[login];
	},

	// Η μέθοδος "oxiSinedria" δέχεται το login name ενός παίκτη και επιστρέφει
	// true εφόσον ΔΕΝ βρεθεί σχετική συνεδρία στο σκηνικό, αλλιώς επιστρέφει
	// false.

	oxiSinedria: function(login) {
		return !this.isSinedria(login);
	},

	// Η μέθοδος "fetchSinedria" δέχεται το login name κάποιου παίκτη και επιστρέφει
	// τη σχετική συνεδρία η οποία ΠΡΕΠΕΙ να είναι ενταγμένη στο σκηνικό.

	fetchSinedria: function(login) {
		var sinedria = Skiniko.isSinedria(login);
		if (!sinedria) globals.fatal(login + ': δεν βρέθηκε συνεδρία στο σκηνικό');
		return sinedria;
	},

	// Η μέθοδος "sinedriaWalk" διατρέχει όλες τις συνεδρίες του σκηνικού και
	// για κάθε συνεδρία καλεί callback function με παραμέτρους το login name
	// του παίκτη και την ίδια τη συνεδρία. Είτε δοθεί είτε δεν δοθεί callback
	// function, επιστρέφεται το πλήθος των συνεδριών που είναι ενταγμένες στο
	// σκηνικό.

	sinedriaWalk: function(callback) {
		return globals.walk(this.sinedria, callback);
	},

	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

	// Η λίστα "pektis" περιέχει παίκτες δεικτοδοτημένους με τα login names τους.
	// Οι παίκτες που κρατούνται στο σκηνικό είναι αυτοί που έχουν αντίστοιχες
	// συνεδρίες και όσοι μετέχουν ως παίκτες στα διάφορα τραπέζια ακόμη και
	// αν δεν έχουν αντίστοιχες συνεδρίες. Στην πορεία φορτώνονται, επίσης, και
	// άλλοι παίκτες μέσω κινήσεων που τους αφορούν, π.χ. αποστολή προσκλήσεων,
	// ανάγνωση πληροφοριών προφίλ κλπ.
	// Η λίστα των παικτών εκκαθαρίζεται από καιρού εις καιρόν ώστε να μην
	// καταλήξουμε κάποια στιγμή να έχουμε φορτωμένους στο σκηνικό όλους τους
	// εγγεγραμμένους παίκτες.

	pektis: {},

	// Η μέθοδος "isPektis" δέχεται το login name ενός παίκτη και εφόσον
	// ο παίκτης είναι στο σκηνικό επιστρέφει τον αντίστοιχο παίκτη, αλλιώς
	// επιστρέφει undefined.

	isPektis: function(login) {
		return this.pektis[login];
	},

	// Η μέθοδος "oxiPektis" δέχεται το login name ενός παίκτη και επιστρέφει
	// true εφόσον δεν βρεθεί ο παίκτης στο σκηνικό, αλλιώς επιστρέφει
	// false.

	oxiPektis: function(login) {
		return !this.isPektis(login);
	},

	// Η μέθοδος "fetchPektis" δέχεται το login name κάποιου παίκτη και επιστρέφει
	// τον σχετικό παίκτη ο οποίος ΠΡΕΠΕΙ να είναι ενταγμένος στο σκηνικό.

	fetchPektis: function(login) {
		var pektis = Skiniko.isPektis(login);
		if (!pektis) globals.fatal(login + ': δεν βρέθηκε παίκτης στο σκηνικό');
		return pektis.pektisEpafi();
	},

	// Η μέθοδος "pektisWalk" διατρέχει όλους τους παίκτες του σκηνικού και
	// για κάθε παίκτη καλεί callback function με παραμέτρους το login name
	// του παίκτη και τον ίδιο τον παίκτη. Είτε δοθεί είτε δεν δοθεί callback
	// function, επιστρέφεται το πλήθος των συνεδριών που είναι ενταγμένες στο
	// σκηνικό.

	pektisWalk: function(callback) {
		return globals.walk(this.pektis, callback);
	},

	// Το property pektisTimeout παίζει σημαντικό ρόλο στην εκκαθάριση τού
	// σκηνικού όσον αφορά στους παίκτες. Τόσο στον server όσο και στους
	// clients η λίστα των παικτών εκκαθαρίζεται από καιρού εις καιρόν,
	// με βάση το συγκεκριμένο χρονικό περιθώριο κατά το οποίο ο παίκτης
	// θα παραμείνει απροσπέλαστος.

	pektisTimeout: 24 * 60 * 60,	// ένα 24ωρο

	// Η μέθοδος "pektisCleanup" εκκαθαρίζει τη λίστα των παικτών τού σκηνικού.
	// Οι παίκτες εκκαθαρίζονται εφόσον δεν έχουν προσπελαστεί την τελευταία
	// μέρα, δεν έχουν αντίστοιχη συνεδρία και δεν μετέχουν ως παίκτες σε κάποιο
	// τραπέζι. Για τους παίκτες οι οποίοι εκκαθαρίζονται μπορεί να κληθεί και
	// callback function την οποία περνάμε ως παράμετρο και πρέπει να δέχεται
	// ως δική της παράμετρο τον ίδιο τον παίκτη.
	// Η μέθοδος επιστρέφει το πλήθος των παικτών που διέγραψε από το σκηνικό.
	// Η μέθοδος καλείται τόσο στον server όσο και στους επιμέρους clients μέσω
	// των διαδικασιών περιπόλου.

	pektisCleanup: function(callback) {
		var count = 0;
		var tora = globals.toraServer();
		Skiniko.pektisWalk(function(login, pektis) {
			if (tora - pektis.used < Skiniko.pektisTimeout) return;

			// Ο παίκτης δείχνει να μην έχει χρησιμοποιηθεί τελευταία.
			// Αν ο παίκτης έχει αντίστοιχη συνεδρία, ή μετέχει ως παίκτης
			// σε κάποιο τραπέζι, τότε ανανεώνουμε το χρόνο παραμονής του
			// στο σκηνικό.

			if (pektis.isSinedria() || pektis.isTrapezi()) {
				pektis.pektisEpafi(tora);
				return;
			}

			// Ο παίκτης μοιάζει να είναι παρωχημένος και η παρουσία του
			// στο σκηνικό δεν είναι απαραίτητη.

console.log('Skiniko.pektisCleanup: ' + login);
			if (callback) callback(pektis);
			pektis.pektisDel();
			count++;
		});

		return count;
	},

	// Η μέθοδος "pektisEpafi" δέχεται το login name κάποιου παίκτη και εφόσον
	// ο παίκτης αυτός είναι ενταγμένος στο σκηνικό, ανανεώνεται ο χρόνος
	// παραμονής του.

	pektisEpafi: function(login) {
		if (Skiniko.isPektis(login)) Skiniko.pektis[login].pektisEpafi();
	},

	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

	// Το array "sizitisi" περιέχει τη συζήτηση που εξελίσσεται στο καφενείο.

	sizitisi: [],

	sizitisiWalk: function(callback) {
		for (var i = 0; i < Skiniko.sizitisi.length; i++) {
			if (callback) callback(i, Skiniko.sizitisi[i]);
		}

		return i;
	},

	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

	// Η λίστα "prosklisi" περιέχει τις προσκλήσεις δεικτοδοτημένες με
	// τον κωδικό πρόσκλησης. Η λίστα είναι εναρμονισμένη με τις λίστες
	// προσκλήσεων που τηρούνται σε κάθε τραπέζι και τηρείται για να
	// έχουμε γρήγορη πρόσβαση στα δεδομένα τής πρόσκληση δοθέντος τού
	// κωδικού της.

	prosklisi: {},

	// Η μέθοδος "isProsklisi" δέχεται έναν κωδικό πρόσκλησης και επιστρέφει
	// την ίδια την πρόσκληση εφόσον είναι ήδη ενταγμένη στο σκηνικό, αλλιώς
	// επιστρέφει undefined.

	isProsklisi: function(kodikos) {
		return this.prosklisi[kodikos];
	},

	// Η μέθοδος "oxiProsklisi" δέχεται έναν κωδικό πρόσκλησης και επιστρέφει
	// true εφόσον η πρόσκληση ΔΕΝ είναι ενταγμένη στο σκηνικό, αλλιώς
	// επιστρέφει false.

	oxiProsklisi: function(kodikos) {
		return !this.isProsklisi(kodikos);
	},

	// Η μέθοδος "fetchProsklisi" δέχεται έναν κωδικό πρόσκλησης και επιστρέφει
	// την ίδια την πρόσκληση η οποία ΠΡΕΠΕΙ να είναι ενταγμένη στο σκηνικό.

	fetchProsklisi: function(kodikos) {
		var prosklisi = Skiniko.isProsklisi(kodikos);
		if (!prosklisi) globals.fatal(kodikos + ': η πρόσκλησηδεν βρέθηκε στο σκηνικό');
		return prosklisi;
	},

	// Η μέθοδος "prosklisiWalk" διατρέχει όλες τις προσκλήσεις του σκηνικού και
	// για κάθε πρόσκληση καλεί callback function με παραμέτρους τον κωδικό
	// της πρόσκλησης και την ίδια την πρόσκληση. Είτε δοθεί είτε δεν δοθεί
	// callback function, επιστρέφεται το πλήθος των προσκλήσεων που είναι
	// ενταγμένες στο σκηνικό.

	prosklisiWalk: function(callback) {
		return globals.walk(this.prosklisi, callback);
	},
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η κλάση "Trapezi" απεικονίζει τα τραπέζια του «Πρεφαδόρου». Κάθε τραπέζι
// έχει ένα σωρό μεθόδους με τις οποίες κάνουμε κάτι με το τραπέζι, από το
// να αναθέσουμε τιμές στα πεδία του, μέχρι την ένταξη του τραπεζιού στο
// σκηνικό.

Trapezi = function(proto) {
	var prop;
	for (prop in proto) this[prop] = proto[prop];

	// Προετοιμάζουμε δομές που θα υποδεχθούν άλλα στοιχεία του τραπεζιού. Ξεκινάμε
	// με τη λίστα παραμέτρων του τραπεζιού που δεικτοδοτείται με την εσωτερική
	// ονομασία των παραμέτρων και φέρει τιμές τις τιμές των αντίστοιχων παραμέτρων.

	this.trparam = {};

	// Ακολουθεί η λίστα προσκλήσεων δεικτοδοτημένη με τους κωδικούς των προσκλήσεων.

	this.prosklisi = {};

	// Ακολουθεί η λίστα των θεατών δεικτοδοτημένη με τα login names των θεατών και
	// τιμές τις αντίστοιχες θέσης θέασης.

	this.theatis = {};

	// Ακολουθεί το array των διανομών. Εδώ χρησιμοποιούμε array καθώς η σειρά παίζει
	// καθοριστικό ρόλο.

	this.dianomi = [];

	// Ακολουθεί το array συζήτησης του τραπεζιού. Εδώ χρησιμοποιείται array καθώς
	// η σειρά των σχολίων είναι σημαντική.

	this.sizitisi = [];

	// Το property "pistosi" δείχνει την συνολική κάσα που έχει κατέβει ή ανέβει.
	// Το υπόλοιπο της κάσας είναι ανά πάσα στιγμή η κάσα μείον την πίστωση.

	this.pistosi = 0;
}

Trapezi.prototype.fetchProp = function(prop) {
	if (this.hasOwnProperty(prop)) return this[prop];
	globals.fatal('Trapezi.fetchProp: "' + prop + '" missing property');
}

// Η μέθοδος "trapeziEpafi" ενημερώνει το property "poll" τού τραπεζιού σύμφωνα
// το timestamp που περνάμε ως παράμετρο. Αν δεν περαστεί παράμετρος
// τίθεται η τρέχουσα χρονική στιγμή.

Trapezi.prototype.trapeziEpafi = function(ts) {
	this.poll = ts ? ts : globals.toraServer();
	return this;
}

// Η μέθοδος "add" εντάσσει ένα τραπέζι στο σκηνικό και επιστρέφει το τραπέζι.

Trapezi.prototype.trapeziAdd = function() {
	this.trapeziEpafi();
	this.fasi = 'ΣΤΗΣΙΜΟ';
	return(Skiniko.trapezi[this.fetchProp('kodikos')] = this);
}

// Η μέθοδος "del" απεντάσσει ένα τραπέζι από το σκηνικό και επανεντοπίζει
// τις συνεδρίες τών παικτών που μετείχαν στο τραπέζι είτε ως παίκτες, είτε
// ως θεατές. Η μέθοδος επιστρέφει τη λίστα των παικτών που μετείχαν στο
// τραπέζι είτε έχουν σχετική συνεδρία, είτε όχι.

Trapezi.prototype.trapeziDel = function() {
	var plist = this.plist(), login, sinedria;

	globals.walk(this.fetchProp('prosklisi'), function(kodikos, prosklisi) {
		prosklisi.prosklisiDel();
	});
	delete Skiniko.trapezi[this.fetchProp('kodikos')];

	for (login in plist) {
		sinedria = Skiniko.isSinedria(login);
		if (sinedria) sinedria.entopismos();
		else delete plist[login];
	}

	return plist;
}

Trapezi.trapeziNeo = function(kodikos, pektis, prosklisi) {
	var sinedria;
	var trapezi;
	var stisimo = globals.toraServer();

	trapezi = new Trapezi({
		'kodikos': kodikos,
		'pektis1': pektis,
		'stisimo': stisimo,
		'poll': stisimo,
	}).trapeziAdd();

	new Prosklisi({
		'kodikos': prosklisi,
		'trapezi': kodikos,
		'apo': pektis,
		'pros': pektis,
		'epidosi': stisimo,
	}).prosklisiAdd();

	sinedria = Skiniko.fetchSinedria(pektis);
	if (sinedria.isTheatis()) Skiniko.fetchTrapezi(sinedria.trapezi).theatisDel(pektis);

	sinedria.trapezi = kodikos;
	sinedria.thesi = 1;
	delete sinedria.theatis;

	return trapezi;
}

Trapezi.prototype.isProskeklimenos = function(login) {
	for (var i in this.prosklisi) {
		if (this.prosklisi[i].pros == login) return true;
	}

	return false;
}

Trapezi.prototype.oxiProskeklimenos = function(login) {
	return !this.isProskeklimenos(login);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η μέθοδος "pektis" δέχεται τον αριθμό θέσης και μας επιστρέφει τον
// παίκτη που κάθεται στη θέση αυτή, ή undefined εφόσον η θέση παραμένει
// κενή.

Trapezi.prototype.pektis = function(thesi) {
	return this['pektis' + thesi];
}

// Η μέθοδος "oxiPektis" δέχεται τον αριθμό θέσης και επιστρέφει true
// αν η θέση στο τραπέζι παραμένει κενή.

Trapezi.prototype.oxiPektis = function(thesi) {
	return !this.pektis(thesi);
}

// Η μέθοδος "thesiPekti" δέχεται το login name ενός παίκτη και επιστρέφει
// τη θέση του παίκτη στο τραπέζι εφόσον ο παίκτης παίζει στο τραπέζι,
// αλλιώς επιστρέφει null.
 
Trapezi.prototype.thesiPekti = function(login) {
	for (var i in Prefadoros.thesiValid) {
		if (this['pektis' + i] == login) return parseInt(i);
	}

	return null;
}

// Η μέθοδος "isPektis" είναι ταυτόσημη με την "thesiPekti".

Trapezi.prototype.isPektis = function(login) {
	return this.thesiPekti(login);
}

// Η μέθοδος "thesiTheati" δέχεται το login name ενός παίκτη και επιστρέφει
// τη θέση του παίκτη ως θεατού στο τραπέζι. Αν ο παίκτης δεν μετέχει ως
// θεατής στο τραπέζι επιστρέφεται undefined.
 
Trapezi.prototype.thesiTheati = function(login) {
	return this.theatis[login];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η μέθοδος "isTheatis" δέχεται το login name ενός παίκτη και επιστρέφει τη
// θέση που παρακολουθεί ο παίκτης στο τραπέζι εφόσον ο παίκτης είναι θεατής,
// αλλιώς επιστρέφει undefined.

Trapezi.prototype.isTheatis = function(login) {
	return this.theatis[login];
}

// Η μέθοδος "oxiTheatis" επιστρέφει true εφόσον ο παίκτης δεν μετέχει ως
// θεατής στο τραπέζι.

Trapezi.prototype.oxiTheatis = function(login) {
	return !this.isTheatis(login);
}

// Η μέθοδος "thesiTheati" δέχεται το login name κάποιου παίκτη και εφόσον
// ο παίκτης είναι θεατής στο τραπέζι, επιστρέφει τη θέση θέασης, αλλιώς
// επιστρέφει undefined.

Trapezi.thesiTheati = function(login) {
	return this.theatis[login];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η μέθοδος "apodoxi" δέχεται τον αριθμό θέσης και επιστρέφει true εφόσον
// υπάρχει αποδοχή όρων στην αντίστοιχη θέση, αλλιώς επιστρέφει false ή undefined.

Trapezi.prototype.apodoxi = function(thesi) {
	return this['apodoxi' + thesi];
}

// Η μέθοδος "oxiApodoxi" δέχεται τον αριθμό θέσης και επιστρέφει true εφόσον
// δεν υπάρχει αποδοχή όρων στην αντίστοιχη θέση, αλλιώς επιστρέφει false.

Trapezi.prototype.oxiApodoxi = function(thesi) {
	return !this.apodoxi(thesi);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η μέθοδος "isKeni" δέχεται τον αριθμό θέσης και μας επιστρέφει true
// εφόσον η θέση παραμένει κενή, αλλιώς επιστρέφει false.

Trapezi.prototype.isKeni = function(thesi) {
	return this.oxiPektis(thesi);
}

// Η μέθοδος "oxiKeni" δέχεται τον αριθμό θέσης και μας επιστρέφει true
// εφόσον η θέση είναι κατειλημμένη, αλλιώς επιστρέφει false.

Trapezi.prototype.oxiKeni = function(thesi) {
	return !this.isKeni(thesi);
}

// Η μέθοδος "keniThesi" επιστρέφει την πρώτη κενή θέση του τραπεζιού. Αν δεν υπάρχει
// κενή θέση επιστρέφει null.

Trapezi.prototype.keniThesi = function() {
	for (var i = 1; i <= Prefadoros.thesiMax; i++) {
		if (!this.pektis(i)) return i;
	}

	return null;
}

// Η μέθοδος "kenes" επιστρέφει το πλήθος των κενών θέσεων του τραπεζιού.

Trapezi.prototype.kenes = function() {
	var keni = 0;
	for (var i in Prefadoros.thesiValid) {
		if (this.isKeni(i)) keni++;
	}

	return keni;
}

// Η μέθοδος "plires" επιστρέφει true εφόσον όλες οι θέσεις του τραπεζιού
// είναι κατειλημμένες.

Trapezi.prototype.plires = function() {
	return !this.kenes();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η μέθοδος "plist" δέχεται ως παράμετρο ένα τραπέζι και επιστρέφει λίστα
// θέσεων συμμετεχόντων δεικτοδοτημένη με τα login names. Εκ της φύσεώς της
// η μέθοδος δίνει προτεραιότητα στους θεατές, αν δηλαδή παρ' ελπίδα κάποιος
// παίκτης εμφανίζεται ως παίκτης ΚΑΙ ως θεατής στο τραπέζι, τότε στη λίστα
// επιστρέφεται η θέση του ως θεατού.

Trapezi.prototype.plist = function() {
	var i, pektis, l = {};

	// Εισάγονται στη λίστα οι παίκτες του τραπεζιού.
	for (i in Prefadoros.thesiValid) {
		pektis = this.pektis(i);
		if (pektis) l[pektis] = parseInt(i);
	}

	// Εισάγονται στη λίστα οι θεατές του τραπεζιού.
	for (i in this.theatis) {
		l[i] = parseInt(this.theatis[i]);
	}

	return l;
}

// Η μέθοδος "llist" δέχεται ως παράμετρο ένα τραπέζι και επιστρέφει λίστα
// των προσκλήσεων τού τραπεζιού.

Trapezi.prototype.llist = function() {
	var i, l = {};

	for (i in this.prosklisi) {
		l[i] = this.prosklisi[i];
	}

	return l;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η μέθοδος "trparamSet" δέχεται το είδος και την τιμή μιας παραμέτρου
// του τραπεζιού και εντάσσει την παράμετρο στο τραπέζι. Αν η τιμή είναι
// η default τιμή της παραμέτρου, τότε η παράμετρος διαγράφεται τελείως.

Trapezi.prototype.trparamSet = function(param, timi) {
//console.log('Trapezi.trparamSet: ', this.kodikos, param, timi);
	var errtimi = false;
	switch (param) {
	case 'ΚΑΣΑ':
		if (isNaN(timi)) errtimi = true;
		else if (parseInt(timi) != timi) errtimi = true;
		else if (timi == globals.defaultKasa) timi = null;
		else timi = parseInt(timi);
		break;
	case 'ΑΣΟΙ':
	case 'ΑΝΟΙΚΤΟ':
		timi = timi.isNai() ? null : 0;
		break;
	case 'ΠΑΣΟ':
	case 'ΠΡΙΒΕ':
	case 'ΦΙΛΙΚΗ':
	case 'ΕΠΕΤΕΙΑΚΗ':
	case 'ΙΔΙΟΚΤΗΤΟ':
		timi = timi.isNai() ? 1 : null;
		break;
	case 'ΤΕΛΕΙΩΜΑ':
		switch (timi) {
		case 'ΚΑΝΟΝΙΚΟ':
			timi = null;
			break;
		case 'ΑΝΙΣΟΡΡΟΠΟ':
			timi = 'U';
			break;
		case 'ΔΙΚΑΙΟ':
			timi = 'F';
			break;
		default:
			errtimi = true;
			break;
		}
		break;
	default:
		globals.fatal('Trapezi.trparamSet: ' + param + ': invalid "param"');
	}

	if (errtimi) globals.fatal('Trapezi.trparamSet: ' + timi + ': invalid "timi" for "' + param + '"');

	if (timi === null) delete this.trparam[param];
	else this.trparam[param] = timi;

	this.trapeziEpafi();
	return this;
}

Trapezi.prototype.kasa = function() {
	var kasa = this.trparam['ΚΑΣΑ'];

	if (parseInt(kasa) != kasa) return globals.defaultKasa;
	return kasa;
}

Trapezi.prototype.ipolipo = function() {
	return parseInt((this.kasa() * 3) - this.pistosi);
}

Trapezi.prototype.isAsoi = function() {
	if (!this.trparam.hasOwnProperty('ΑΣΟΙ')) return true;
	return this.trparam['ΑΣΟΙ'];
}

Trapezi.prototype.oxiAsoi = function() {
	return !this.isAsoi();
}

Trapezi.prototype.isPaso = function() {
	return this.trparam['ΠΑΣΟ'];
}

Trapezi.prototype.oxiPaso = function() {
	return !this.isPaso();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

Trapezi.prototype.telioma = function() {
	switch (this.trparam['ΤΕΛΕΙΩΜΑ']) {
	case 'U':
	case 'F':
		return this.trparam['ΤΕΛΕΙΩΜΑ'];
	default:
		return 'N';
	}
}

Trapezi.prototype.teliomaKanoniko = function() {
	return(this.telioma() == 'N');
}

Trapezi.prototype.teliomaAnisoropo = function() {
	return(this.telioma() == 'U');
}

Trapezi.prototype.teliomaDikeo = function() {
	return(this.telioma() == 'F');
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

Trapezi.prototype.isPrive = function() {
	return this.trparam['ΠΡΙΒΕ'];
}

Trapezi.prototype.isDimosio = function() {
	return !this.isPrive();
}

Trapezi.prototype.isAnikto = function() {
	if (!this.trparam.hasOwnProperty('ΑΝΟΙΚΤΟ')) return true;
	return this.trparam['ΑΝΟΙΚΤΟ'];
}

Trapezi.prototype.isKlisto = function() {
	return !this.isAnikto();
}

Trapezi.prototype.isFiliki = function() {
	return this.trparam['ΦΙΛΙΚΗ'];
}

Trapezi.prototype.isAgonistiki = function() {
	return !this.isFiliki();
}

Trapezi.prototype.isEpetiaki = function() {
	return this.trparam['ΕΠΕΤΕΙΑΚΗ'];
}

Trapezi.prototype.oxiEpetiaki = function() {
	return !this.isEpetiaki();
}

Trapezi.prototype.isIdioktito = function() {
	return this.trparam['ΙΔΙΟΚΤΗΤΟ'];
}

Trapezi.prototype.isElefthero = function() {
	return !this.isIdioktito();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

Trapezi.prototype.pektisSet = function(login, thesi) {
	this['pektis' + thesi] = login;
	Skiniko.pektisEpafi(login);
	return this.trapeziEpafi();
}

// Η μέθοδος "theatisSet" ενημερώνει τη λίστα θεατών του τραπεζιού καθιστώντας
// θεατή τον παίκτη τού οποίου το login name περνάμε ως παράμετρο. Μπορούμε να
// περάσουμε και τη θέση θέασης ως δεύτερη παράμετρο με default τη θέση 1.

Trapezi.prototype.theatisSet = function(login, thesi) {
	this.theatis[login] = (thesi === undefined ? 1 : Prefadoros.fetchThesi(thesi));
	Skiniko.pektisEpafi(login);
	// Δεν ενημερώνουμε το χρόνο επαφής τού τραπεζιού,
	// ώστε να μην έχουμε ανανέωση σε παρωχημένα τραπέζια.
	return this;
}

Trapezi.prototype.theatisDel = function(login) {
	delete this.theatis[login];
	Skiniko.pektisEpafi(login);
	// Δεν ενημερώνουμε το χρόνο επαφής τού τραπεζιού,
	// ώστε να μην έχουμε ανανέωση σε παρωχημένα τραπέζια.
	return this;
}

Trapezi.prototype.pektisDel = function(thesi) {
	delete this['pektis' + thesi];
	return this;
}

Trapezi.prototype.sizitisiDiagrafi = function(ola) {
	var sizitisi, i;

	if (ola) {
		this.sizitisi = [];
		return this;
	}

	sizitisi = this.sizitisi;
	for (i = sizitisi.length - 1; i >= 0; i--) {
		if (sizitisi[i].kimeno == Prefadoros.sxolioKodikos.moliviKafenio) continue;
		if (sizitisi[i].kimeno == Prefadoros.sxolioKodikos.moliviTrapezi) continue;
		sizitisi.splice(i, 1);
		break;
	}

	return this;
}

// Η μέθοδος "dianomiAdd" εισάγει νέα διανομή στο τραπέζι. Η διανομή προστίθεται
// στο array διανομών τού τραπεζιού και ενημερώνεται η πίστωση τής κάσας.

Trapezi.prototype.dianomiAdd = function(dianomi) {
	this.dianomi.push(dianomi);
	this.pistosi += parseInt((dianomi.kasa1 + dianomi.kasa2 + dianomi.kasa3) / 10);
	return this;
}

// Η μέθοδος "telefteaDianomi" επιστρέφει την τελευταία διανομή τού τραπεζιού.

Trapezi.prototype.telefteaDianomi = function() {
	if (this.oxiDianomi()) globals.fatal('Trapezi.telefteaDianomi: ' + this.kodikos + ': δεν υπάρχουν διανομές');
	return this.dianomi[this.dianomi.length - 1];
}

// Η μέθοδος "telefteaEnergia" επιστρέφει την τελευταία ενέργεια τής τελευταίας
// διανομής τού τραπεζιού.

Trapezi.prototype.telefteaEnergia = function() {
	return this.telefteaDianomi().telefteaEnergia();
}

// Η μέθοδος "energiaAdd" εισάγει νέα ενέργεια στην τελευταία διανομή τού
// τραπεζιού. Ουσιαστικά προστίθεται νέα ενέργεια στο array ενεργειών τής
// τελευταίας διανομής.

Trapezi.prototype.energiaAdd = function(energia) {
	var dianomi, elist;

	// Προστίθεται η ενέργεια στο array ενεργειών της τελευταίας διανομής
	// και ανάλογα με το είδος τής ενέργειας καλούμε εξειδικευμένη μέθοδο
	// εφαρμογής των συνεπειών της συγκεκριμένης ενέργειας σε διάφορα
	// στοιχεία τού τραπεζιού.

	dianomi = this.telefteaDianomi();
	elist = dianomi.fetchProp('energia');
	elist.push(energia);

	// Αν υπάρχει μέθοδος εφαρμογής συνεπειών για το συγκεκριμένο είδος
	// ενέργειας, τότε εφαρμόζεται στο τραπέζι για την τελευταία ενέργεια.

	if (!Trapezi.energiaAddMap.hasOwnProperty(energia.idos))
		globals.fatal(energia.idos + ': απρόβλεπτη ενέργεια');

	Trapezi.energiaAddMap[energia.idos].call(this, energia, elist, dianomi);

	// Στο σημείο αυτό θα καθοριστεί ο επόμενος παίκτης, δηλαδή ο παίκτης
	// που θα πρέπει να κάνει την επόμενη ενέργεια. Παράλληλα, καθορίζεται
	// και η τρέχουσα φάση τού παιχνιδιού.

	this.setEpomenos();

	return this;
}

Trapezi.prototype.isDianomi = function() {
	return this.dianomi.length;
}

Trapezi.prototype.oxiDianomi = function() {
	return !this.isDianomi();
}

// Η μέθοδος "isTzogos" επιστρέφει true εφόσον στο τραπέζι υπάρχουν καθορισμένα
// φύλλα τζόγου. Θυμίζουμε ότι τα φύλλα του τζόγου είναι χαρτωσιά δύο φύλλων και
// βρίσκονται στο property "tzogos" του τραπεζιού.

Trapezi.prototype.isTzogos = function() {
	if (!this.hasOwnProperty('tzogos')) return false;
	if (this.tzogos.fila.length != 2) return false;
	return this.tzogos;
}

// Η μέθοδος "oxiTzogos" επιστρέφει true εφόσον δεν υπάρχουν καθορισμένα φύλλα
// τζόγου στο ανά χείρας τραπέζι.

Trapezi.prototype.oxiTzogos = function() {
	return !this.isTzogos();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η κλάση "Sinedria" απεικονίζει τις συνεδρίες, δηλαδή τους ενεργούς παίκτες του
// «Πρεφαδόρου». Κάθε συνεδρία έχει ένα σωρό μεθόδους με τις οποίες κάνουμε κάτι
// με αυτήν, από το να ορίσουμε τα πεδία της μέχρι να την εντάξουμε στο σκηνικό.

Sinedria = function(sinedria) {
	for (var i in sinedria) this[i] = sinedria[i];
}

Sinedria.prototype.fetchProp = function(prop) {
	if (this.hasOwnProperty(prop)) return this[prop];
	globals.fatal('Sinedria.fetchProp: "' + prop + '" missing property');
}

// Η μέθοδος "add" εντάσσει μια συνεδρία στο σκηνικό και επιστρέφει την
// ίδια τη συνεδρία.

Sinedria.prototype.sinedriaAdd = function() {
	Skiniko.pektisEpafi(this.fetchProp('pektis'));
	return(Skiniko.sinedria[this.pektis] = this.sinedriaThesiFix());
}

Sinedria.prototype.sinedriaThesiFix = function() {
	if (!this.hasOwnProperty('trapezi')) {
		delete this.thesi;
		delete this.theatis;
		return this;
	}

	if (!this.hasOwnProperty('thesi')) {
		delete this.trapezi;
		delete this.theatis;
		return this;
	}

	if (Prefadoros.oxiThesi(this.thesi)) {
		delete this.trapezi;
		delete this.theatis;
		return this;
	}

	if (Skiniko.oxiTrapezi(this.trapezi)) {
		delete this.trapezi;
		delete this.thesi;
		delete this.theatis;
		return this;
	}

	return this;
}

// Η μέθοδος "del" απεντάσσει μια συνεδρία από το σκηνικό και επιστρέφει
// την συνεδρία.

Sinedria.prototype.sinedriaDel = function() {
	delete Skiniko.sinedria[this.fetchProp('pektis')];
	Skiniko.pektisEpafi(this.pektis);
	return this;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η μέθοδος "trapeziSet" θέτει τα στοιχεία θέσης της συνεδρίας. Πιο συγκεκριμένα,
// τίθεται ο κωδικός τραπεζιού στο οποίο μετέχει ο παίκτης, η θέση του στο τραπέζι
// και το αν συμμετέχει ως παίκτης ή ως θεατής. Η μέθοδος επιστρέφει τη συνεδρία.
// Αν δεν περάσουμε παραμέτρους, σημαίνει ότι ο παίκτης είναι περιφερόμενος.

Sinedria.prototype.trapeziSet = function(trapeziKodikos, thesi, theatis) {
	var login = this.fetchProp('pektis');
	Skiniko.pektisEpafi(login);

	if (!arguments.length) {
		for (var i in Skiniko.trapezi) {
			if (Skiniko.trapezi[i].thesiPekti(login) || Skiniko.trapezi[i].thesiTheati(login))
				globals.fatal('Sinedria.trapeziSet: τίθεται περιφερόμενος ενώ συμμετέχει');
		}

		delete this.trapezi;
		delete this.thesi;
		delete this.theatis;
		return this;
	}

	var trapezi = Skiniko.fetchTrapezi(trapeziKodikos);
	if (Prefadoros.oxiThesi(thesi)) globals.fatal('Sinedria.trapeziSet: ' + thesi + ': απαράδεκτη θέση');

	this.trapezi = trapezi.kodikos;
	this.thesi = parseInt(thesi);

	if (theatis) {
		trapezi.theatisSet(login, thesi);
		this.theatis = 1;
		return this;
	}

	if (trapezi.pektis(thesi) != login) globals.fatal('Sinedria.trapeziSet: λανθασμένα στοιχεία θέσης παίκτη');
	delete this.theatis;
	return this;
}

Sinedria.prototype.isTrapezi = function() {
	if (!this.trapezi) return false;
	if (Prefadoros.oxiThesi(this.thesi)) globals.fatal('Sinedria.isTrapezi: ' + this.pektis + ': ακαθόριστη θέση');
	if (Skiniko.oxiTrapezi(this.trapezi)) globals.fatal('Sinedria.isTrapezi: ' + this.pektis + ': ανύπαρκτο τραπέζι');
	return this.trapezi;
}

Sinedria.prototype.oxiTrapezi = function() {
	return !this.isTrapezi();
}

Sinedria.prototype.fetchTrapezi = function() {
	return Skiniko.fetchTrapezi(this.isTrapezi());
}

Sinedria.prototype.isThesi = function() {
	if (!this.thesi) {
		if (this.theatis) globals.fatal('Sinedria.isThesi: ' + this.pektis + ': ακαθόριστη θέση θέασης');
		if (this.trapezi) globals.fatal('Sinedria.isThesi: ' + this.pektis + ': ακαθόριστη θέση παίκτη');
		return false;
	}

	if (Prefadoros.oxiThesi(this.thesi)) globals.fatal('Sinedria.isThesi: ' + this.pektis + ': ' +
		this.thesi + ': λανθασμένη θέση');
	if (!this.trapezi) globals.fatal('Sinedria.isThesi: ' + this.pektis + ': ακαθόριστο τραπέζι');
	if (Skiniko.oxiTrapezi(this.trapezi)) globals.fatal('Sinedria.isTrapezi: ' + this.pektis + ': ανύπαρκτο τραπέζι');
	return parseInt(this.thesi);
}

Sinedria.prototype.oxiThesi = function() {
	return !this.isThesi();
}

Sinedria.prototype.fetchThesi = function() {
	var thesi = this.isThesi();
	if (thesi) return thesi;
	globals.fatal('Sinedria.fetchThesi: ακαθόριστη θέση');
}

Sinedria.prototype.isTheatis = function() {
	if (!this.theatis) return 0;
	if (this.isTrapezi()) return 1;
	globals.fatal('Sinedria.isTheatis: ' + this.pektis + ': θεατής σε ακαθόριστο τραπέζι');
}

Sinedria.prototype.oxiTheatis = function() {
	return !this.isTheatis();
}

// Η μέθοδος "isPektis" επιστρέφει true εφόσον τα στοιχεία θέσης τής ανά χείρας
// συνεδρίας δείχνουν παίκτη σε τραπέζι, αλλιώς επιστρέφει false. Αν περάσουμε
// και θέση, τότε επιστρέφει true μόνο εφόσον τα στοιχεία θέσης δείχνουν παίκτη
// και μάλιστα στη συγκεκριμένη θέση.

Sinedria.prototype.isPektis = function(thesi) {
	if (this.oxiTrapezi()) return false;
	if (this.isTheatis()) return false;
	if (thesi === undefined) return true;
	return(thesi == this.thesi);
}

Sinedria.prototype.oxiPektis = function() {
	return !this.isPektis();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η μέθοδος "entopismos" θέτει αυτόματα τα στοιχεία θέσης της συνεδρίας
// με βάση τα στοιχεία των τραπεζιών. Πιο συγκεκριμένα, διατρέχει όλα τα
// τραπέζια και αν βρει τον παίκτη ως θεατή σε κάποια από αυτά, θέτει το
// νεότερο από αυτά ως τρέχον τραπέζι θέασης. Αν δεν εντοπίσει τον παίκτη
// ως θεατή αλλά τον εντοπίσει ως παίκτη σε κάποια τραπέζια, θέτει το
// νεότερο από αυτά ως τρέχον τραπέζι του παίκτη. Αν ο παίκτης δεν εντοπιστεί
// σε κανένα τραπέζι είτε ως θεατής είτε ως παίκτης, τότε τίθενται κενά
// στοιχεία θέσης, δηλαδή ο παίκτης καθίσταται περιφερόμενος.

Sinedria.prototype.entopismos = function() {
	// Αρχικά καθιστούμε τον παίκτη περιφερόμενο όχι όμως με την μέθοδο
	// "trapeziSet" για να αποφύγουμε τυχόν παρενέργειες.

	delete this.trapezi;
	delete this.thesi;
	delete this.theatis;

	// Θα διατρέξουμε τα τραπέζια τού σκηνικού προσπαθώντας να εντοπίσουμε
	// τον παίκτη της συνεδρίας πρώτα ως θεατή και δευτερυόντως ως παίκτη
	// σε κάποιο από αυτά. Αρχικά θεωρούμε ότι δεν συμμετέχει σε κανένα
	// τραπέζι ούτε ως θεατής, ούτε ως παίκτης.

	var trapeziFound = 0;
	var thesiFound = null;
	var theatis = 0;

	var pektis = this.pektis;
	Skiniko.trapeziWalk(function(kodikos, trapezi) {
		// Πρέπει να περάσουμε τον κωδικό ως αριθμό και όχι ως string
		// για να έχουμε σωστό έλεγχο τάξης.

		kodikos = parseInt(kodikos);

		// Ελέγχουμε πρώτα αν ο παίκτης εμφανίζεται ως θεατής στο
		// ανά χείρας τραπέζι.

		var thesi = trapezi.thesiTheati(pektis);
		if (thesi) {
			// Αν δεν έχει εντοπιστεί ήδη ως θεατής, τότε το
			// τραπέζι αυτό υπερτερεί.

			if (!theatis) {
				trapeziFound = kodikos;
				thesiFound = thesi;
				theatis = 1;
				return;
			}

			// Ο παίκτης είχε ήδη εντοπιστεί σε άλλο τραπέζι ως
			// θεατής, επομένως ελέγχουμε αν το παρόν τραπέζι
			// είναι νεότερο.

			if (trapeziFound < kodikos) {
				trapeziFound = kodikos;
				thesiFound = thesi;
				return;
			}

			// Τα προηγούμενα στοιχεία θέσης του παίκτη ως θεατού
			// υπερτερούν.

			return;
		}

		// Ο παίκτης δεν μετέχει ως θεατής στο ανά χείρας τραπέζι. Αν
		// ο παίκτης έχει ήδη εντοπιστεί ως θεατής σε άλλο τραπέζι,
		// εκείνο το τραπέζι υπερτερεί.

		if (theatis) return;

		// Αν ο παίκτης ΔΕΝ συμμετέχει ούτε ως παίκτης στο τραπέζι
		// προχωρούμε στο επόμενο τραπέζι.

		thesi = trapezi.thesiPekti(pektis);
		if (!thesi) return;

		// Ο παίκτης μετέχει ως παίκτης στο ανά χείρας τραπέζι. Αν
		// αυτό το τραπέζι είναι νεότερο από τυχόν προηγούμενο στο
		// οποίο έχει ήδη εντοπιστεί, τότε υπερτερεί το παρόν.

		if (trapeziFound < kodikos) {
			trapeziFound = kodikos;
			thesiFound = thesi;
		}
	});

	if (trapeziFound) this.trapeziSet(trapeziFound, thesiFound, theatis);
	return this;
}

Sinedria.prototype.prosklisiDikeoma = function() {
	if (this.oxiTrapezi()) return false;
	if (this.isPektis()) return true;
	if (Skiniko.fetchPektis(this.pektis).isDiaxiristis()) return true;
	return false;
}

Sinedria.prototype.oxiProsklisiDikeoma = function() {
	return !this.prosklisiDikeoma();
}

// Η μέθοδος "apodoxiProsklisis" εφαρμόζεται πάνω στη συνεδρία τού
// παίκτη που αποδέχεται την πρόσκληση. Ως παράμετρο δεν περνάμε την
// πρόσκληση, αλλά το τραπέζι στο οποίο αφορά η πρόσκληση και τη θέση
// στην οποία θα τοποθετηθεί ο παίκτης/θεατής.

Sinedria.prototype.apodoxiProsklisis = function(trapezi, thesi) {
	var tora = {}, meta = {};

	tora.trapezi = this.trapezi;
	tora.thesi = this.thesi;
	tora.theatis = this.theatis ? 1 : 0;

	meta.trapezi = trapezi.kodikos;
	meta.thesi = thesi;
	meta.theatis = trapezi.oxiKeni(thesi) ? 1 : 0;

	// Αν τα τρέχοντα στοιχεία θέσης τού παίκτη είναι ίδια με
	// αυτά που πρόκειται να προκύψουν μετά την αλλαγή, τότε
	// δεν κάνουμε καμία περαιτέρω ενέργεια.

	if (JSON.stringify(tora) == JSON.stringify(meta)) return this;

	// Υπάρχουν διαφορές, επομένως θα πρέπει να κάνουμε όλες εκείνες τις
	// ενέργειες που απαιτούνται για την αλλαγή τραπεζιού/θέσης/ιδιότητας.

	// Αν ο παίκτης είναι τώρα θεατής, πρέπει να τον απαλλάξουμε από
	// αυτή του την ιδιότητα στο τραπέζι που παρακολουθεί. Αν είναι
	// παίκτης σε κάποιο τραπέζι, δεν χρειάζεται να κάνουμε κάτι.

	if (tora.theatis) Skiniko.fetchTrapezi(tora.trapezi).theatisDel(this.pektis);

	// Αν στο τραπέζι στο οποίο θα μεταβεί, μεταβαίνει ως θεατής, θα
	// πρέπει να τον εντάξουμε στη λίστα των θεατών του συγκεκριμένου
	// τραπεζιού.

	if (meta.theatis) trapezi.theatisSet(this.pektis, meta.thesi);

	// Αλλιώς μεταβαίνει ως παίκτης, οπότε τοποθετούμε τον παίκτη στη
	// συγκεκριμένη θέση του τραπεζιού.

	else trapezi.pektisSet(this.pektis, meta.thesi);

	this.trapeziSet(trapezi.kodikos, meta.thesi, meta.theatis);
	return this;
}

Sinedria.prototype.trapeziIsodos = function(trapezi) {
	var thesi;

	// Αν η συνεδρία είναι θεατής σε κάποιο τραπέζι, θα πρέπει να διαγραφεί
	// από θεατής αυτού του τραπεζιού.

	if (this.isTheatis()) Skiniko.fetchTrapezi(this.trapezi).theatisDel(this.pektis);

	// Ελέγχουμε τώρα αν η συνεδρία μετέχει ως παίκτης στο τραπέζι προορισμού.

	thesi = trapezi.thesiPekti(this.pektis);

	// Αν ναι, δεν έχουμε να κάνουμε τίποτα με το τραπέζι και απλώς θέτουμε
	// τα στοιχεία θέσης της συνεδρίας.

	if (thesi) this.trapeziSet(trapezi.kodikos, thesi);

	// Αλλιώς τοποθετούμε τη συνεδρία ως θεατή στο τραπέζι προορισμού και
	// θέτουμε ανάλογα τα στοιχεία θέσης της συνεδρίας.

	else {
		trapezi.theatisSet(this.pektis);
		this.trapeziSet(trapezi.kodikos, 1, true);
	}

	return this;
}

// Η μέθοδος "trapeziExodos" επιχειρεί αποσυσχέτιση τής συνεδρίας από το
// τραπέζι με το οποίο σχετίζεται. Αν πρόκειται για τον τελευταίο παίκτη
// του τραπεζιού, τότε διαγράφεται το τραπέζι και επανεντοπίζονται όλες
// οι συνεδρίες που ήσαν συσχετισμένες με το τραπέζι. Επιστρέφεται λίστα
// των συνεδριών που επανεντοπίστηκαν.

Sinedria.prototype.trapeziExodos = function() {
	var trapezi, login, sinedria,

	// Στη λίστα "plist" προτίθεμαι να εντάξω όλες τις συνεδρίες που
	// θα επανεντοπιστούν με αφορμή την έξοδο τού παίκτη από το τραπέζι.

	plist = {};

	trapezi = Skiniko.fetchTrapezi(this.trapezi); 

	if (this.isTheatis()) {
		trapezi.theatisDel(this.pektis);
	}
	else {
		trapezi.pektisDel(this.thesi);

		// Αν η έξοδος τού παίκτη αφήνει το τραπέζι κενό, τότε θα πρέπει
		// το τραπέζι να διαγραφεί και σ' αυτή την περίπτωση η λίστα των
		// συνεδριών που θα επανεντοπιστούν θα περιλαμβάνει όλους τους
		// εμπλεκόμενους παίκτες και θεατές.

		if (trapezi.kenes() == Prefadoros.thesiMax) plist = trapezi.trapeziDel();
	}

	// Η λίστα των συνεδριών που θα πρέπει να επανεντοπιστούν θα περιλαμβάνει,
	// πάντως, τον παίκτη/θεατή που εξέρχεται από το τραπέζι. Η τιμή δεν παίζει
	// κάποιον ιδιαίτερο ρόλο γι' αυτό βάζω null.

	plist[this.pektis] = null;

	// Διατρέχω τη λίστα των συνεδριών που θα πρέπει να επανεντοπιστούν και, εφόσον
	// υφίστανται πράγματι οι συνεδρίες αυτές, τις επανεντοπίζω.

	for (login in plist) {
		sinedria = Skiniko.isSinedria(login);
		if (sinedria) sinedria.entopismos();
		else delete plist[login];
	}

	return plist;
}

// Η μέθοδος "pektisTheatis" μετατρέπει τη συνεδρία παίκτου τραπεζιού σε
// θεατή της ιδίας θέσης του ιδίου τραπεζιού και επιστρέφει τη συνεδρία.

Sinedria.prototype.pektisTheatis = function() {
	var trapezi = Skiniko.fetchTrapezi(this.trapezi);
	delete trapezi['pektis' + this.thesi];
	trapezi.theatisSet(this.pektis, this.thesi);
	this.theatis = 1;
	return this;
}

// Η μέθοδος "theatisPektis" επιχειρεί να μετατρέψει τη συνεδρία θεατού σε
// παίκτη τού ιδίου τραπεζιού.

Sinedria.prototype.theatisPektis = function(thesi) {
	var trapezi, thesi;

	trapezi = Skiniko.isTrapezi(this.trapezi);
	if (!trapezi) return null;
	if (trapezi.oxiKeni(thesi)) return null;

	// Διαγράφουμε τη συνεδρία από θεατή και την εντάσσουμε ως παίκτη
	// στη θέση που έχει προκύψει από την προηγηθείσα διαδικασία.

	trapezi.theatisDel(this.pektis).pektisSet(this.pektis, thesi);

	// Δεν χρειάζεται πλήρης επανεντοπισμός, παρά διόρθωση τής
	// θέσης και τής ιδιότητας.

	this.thesi = thesi;
	delete this.theatis;

	return this;
}

// Η μέθοδος "kapouPektis" επιστρέφει τον κωδικό τραπεζιού στο οποίο ο παίκτης
// της συνεδρίας εμφανίζεται ως παίκτης, αλλιώς επιστρέφει false. Αν ο παίκτης
// εμφανίζεται ως παίκτης σε περισσότερα από ένα τραπέζια, επιστρέφεται τυχαίο
// τραπέζι και όχι το παλαιότερο, ή το πιο φρέσκο κλπ.

Sinedria.prototype.kapouPektis = function() {
	var kodikos, trapezi, thesi;
	for (kodikos in Skiniko.trapezi) {
		trapezi = Skiniko.fetchTrapezi(kodikos);
		for (thesi in Prefadoros.thesiValid) {
			if (trapezi.pektis(thesi) == this.pektis) return kodikos;
		}
	}

	return false;
}

// Η μέθοδος "pouthenaPektis" επιστρέφει true εφόσον ο παίκτης της συνεδρίας δεν
// εμφανίζεται ως παίκτης σε κανένα τραπέζι. Αν ο παίκτης παίζει σε τουλάχιστον
// ένα τραπέζι, επιστρέφει false.

Sinedria.prototype.pouthenaPektis = function() {
	return !this.kapouPektis();
}

Sinedria.prototype.thesiTheasis = function(thesi) {
	if (this.mustTheatis().thesi == thesi) return this;
	return this.trapeziSet(this.trapezi, thesi, true);
}

Sinedria.prototype.mustTheatis = function() {
	if (this.isTheatis()) return this;
	globals.fatal(this.pektis + ': δεν είναι θεατής');
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η κλάση "Pektis" απεικονίζει τους παίκτες. Κάθε παίκτης έχει ένα σωρό μεθόδους
// με τις οποίες κάνουμε κάτι με αυτόν, από το να ορίσουμε τα πεδία του μέχρι να
// τον εντάξουμε στο σκηνικό.

Pektis = function(login) {
	if (login) this.login = login;

	// Κάθε παίκτης συνοδεύεται από μια σειρά παραμέτρων. Αυτές οι παράμετροι
	// κρατούνται στη λίστα "peparam" που δεικτοδοτείται με το είδος τής
	// παραμέτρου και φέρει τιμή την εσωτερική τιμή τής παραμέτρου, π.χ.
	// αν κάποιος παίκτης έχει προτίμηση στην ανισόρροπη πληρωμή τής τελευταίας
	// διανομής, τότε στην database θα έχουμε τιμή "ΑΝΙΣΟΡΡΟΠΟ" για την
	// παράμετρο "ΤΕΛΕΙΩΜΑ", ενώ στην λίστα "peparam" θα βρούμε στοιχείο
	// με δείκτη "ΤΕΛΕΙΩΜΑ" και τιμή "U".

	this.peparam = {};

	// Κάθε παίκτης συνοδεύεται από πληροφορίες προφίλ. Οι πληροφορίες
	// προφίλ κρατούνται στη λίστα "profinfo" και δεικτοδοτούνται με
	// το login name του συγγραφέα.

	this.profinfo = {};

	// Οι παίκτες μπορούν να σχετίζονται με τους συμπαίκτες τους με σχέσεις
	// αγάπης και μίσους. Η λίστα "sxesi" περιέχει τους σχετιζόμενους
	// παίκτες δεικτοδοτημένους με το login name τού σχετιζομένου και
	// με τιμές "F" για φίλους και "B" για τους αποκλεισμένους.

	this.sxesi = {};

	// Μας ενδιαφέρει η χρονικη στιγμή κατά την οποία προσπελάσαμε τελευταία
	// τον συγκεκριμένο παίκτη στο σκηνικό, καθώς από καιρού εις καιρόν
	// κάνουμε εκκαθάριση των παικτών από το σκηνικό ώστε να αποφύγουμε
	// κατά το δυνατόν τη συνεχή παρουσία όλων των παικτών στο σκηνικό.

	this.used = globals.toraServer();
}

Pektis.prototype.fetchProp = function(prop) {
	if (this.hasOwnProperty(prop)) return this[prop];
	globals.fatal('Pektis.fetchProp: "' + prop + '" missing property');
}

// Η μέθοδος "pektisEpafi" ενημερώνει το property "used" τού παίκτη σύμφωνα
// το timestamp που περνάμε ως παράμετρο. Αν δεν περαστεί παράμετρος
// τίθεται η τρέχουσα χρονική στιγμή.

Pektis.prototype.pektisEpafi = function(ts) {
	this.used = ts ? ts : globals.toraServer();
	return this;
}

// Η μέθοδος "add" εντάσσει έναν παίκτη στο σκηνικό και επιστρέφει τον
// ίδιο τον παίκτη.

Pektis.prototype.pektisAdd = function() {
	this.pektisEpafi();
	return(Skiniko.pektis[this.login] = this);
}

// Η μέθοδος "del" απεντάσσει έναν παίκτη από το σκηνικό και επιστρέφει
// τον παίκτη.

Pektis.prototype.pektisDel = function() {
	delete Skiniko.pektis[this.login];
	return this;
}

// Η μέθοδος "pektisEntopismos" ελέγχει αν υπάρχει συνεδρία για τον ανά χείρας
// παίκτη και αν ναι, επανεντοπίζει τη συνεδρία. Σε κάθε περίπτωση η μέθοδος
// επιστρέφει τον παίκτη.

Pektis.prototype.pektisEntopismos = function() {
	var sinedria = Skiniko.isSinedria(this.login);
	if (sinedria) sinedria.entopismos();
	return this;
}

Pektis.prototype.peparamDecode = function(param) {
	return Prefadoros.peparamDecode(param, this.fetchProp('peparam')[param]);
}

Pektis.prototype.profinfoUpdate = function(sxoliastis, kimeno) {
	if (kimeno) this.profinfo[sxoliastis] = kimeno;
	else delete this.profinfo[sxoliastis];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

Pektis.prototype.peparamSet = function(param, timi) {
//console.log('Pektis.peparamSet: ', this.login, param, timi);
	var errtimi = false;
	switch (param) {
	case 'ΑΞΙΩΜΑ':
		switch (timi) {
		case 'ΘΑΜΩΝΑΣ':
			timi = null;
			break;
		case 'VIP':
			timi = 1;
			break;
		case  'ΕΠΟΠΤΗΣ':
			timi = 2;
			break;
		case 'ΔΙΑΧΕΙΡΙΣΤΗΣ':
			timi = 3;
			break;
		case 'ADMINISTRATOR':
			timi = 4;
			break;
		case 'ΠΡΟΕΔΡΟΣ':
			timi = 5;
			break;
		default:
			errtimi = true;
			break;
		}
		break;
	case 'ΑΣΟΙ':
		timi = timi.isNai() ? null : 0;
		break;
	case 'ΠΑΣΟ':
		timi = timi.isNai() ? 1 : null;
		break;
	case 'ΤΕΛΕΙΩΜΑ':
		switch (timi) {
		case 'ΚΑΝΟΝΙΚΟ':
			timi = null;
			break;
		case 'ΑΝΙΣΟΡΡΟΠΟ':
			timi = 'U';
			break;
		case 'ΔΙΚΑΙΟ':
			timi = 'F';
			break;
		default:
			errtimi = true;
			break;
		}
		break;
	case 'ΠΛΑΤΗ':
		switch (timi) {
		case 'ΜΠΛΕ':
			timi = null;
			break;
		case 'ΚΟΚΚΙΝΟ':
			timi = 'R';
			break;
		case 'ΤΥΧΑΙΟ':
			timi = (globals.torams() % 2) ? 'R' : 'B';
			break;
		default:
			errtimi = true;
			break;
		}
		break;
	case 'ΚΑΤΑΣΤΑΣΗ':
		switch (timi) {
		case 'ΔΙΑΘΕΣΙΜΟΣ':
			timi = null;
			break;
		case 'ΑΠΑΣΧΟΛΗΜΕΝΟΣ':
			timi = 'B';
			break;
		default:
			errtimi = true;
			break;
		}
		break;
	case 'BLOCKIMAGE':
		timi = timi.isNai() ? 1 : null;
		break;
	case 'ΠΑΡΑΣΚΗΝΙΟ':
		if (timi == globals.defaultParaskinio) timi = null;
		else if ((typeof timi) != 'string') errtimi = true;
		break;
	case 'MOVIETIME':
		switch (timi) {
		case 'ΜΕΤΡΟΝΟΜΟΣ':
			timi = null;
			break;
		case 'ΠΡΑΓΜΑΤΙΚΟΣ':
			timi = 'R';
			break;
		default:
			errtimi = true;
			break;
		}
		break;
	case 'MOVIESCALE':
		if (isNaN(timi)) errtimi = true;
		else if (parseInt(timi) != timi) errtimi = true;
		else if (timi < 1) timi = 1;
		else if (timi == globals.defaultMoviescale) timi = null;
		else timi = parseInt(timi);
		break;
	default:
		globals.fatal('Pektis.peparamSet: ' + param + ': invalid "param"');
	}

	if (errtimi) globals.fatal('Pektis.peparamSet: ' + timi + ': invalid "timi" for "' + param + '"');

	if (timi === null) delete this.peparam[param];
	else this.peparam[param] = timi;

	return this;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

Pektis.prototype.axioma = function() {
	if (!this.peparam.hasOwnProperty('ΑΞΙΩΜΑ')) return 0;

	switch (this.peparam['ΑΞΙΩΜΑ']) {
	case 0:
	case 1:
	case 2:
	case 3:
	case 4:
	case 5:
		return this.peparam['ΑΞΙΩΜΑ'];
	}

	globals.fatal('Pektis.axioma: ' + this.peparam['ΑΞΙΩΜΑ'] + ': invalid internal value');
}

Pektis.prototype.isVip = function() {
	return(this.axioma() >= 1);
}

Pektis.prototype.oxiVip = function() {
	return !this.isVip();
}

Pektis.prototype.isEpoptis = function() {
	return(this.axioma() >= 2);
}

Pektis.prototype.oxiEpoptis = function() {
	return !this.isEpoptis();
}

Pektis.prototype.isDiaxiristis = function() {
	return(this.axioma() >= 3);
}

Pektis.prototype.oxiDiaxiristis = function() {
	return !this.isDiaxiristis();
}

Pektis.prototype.isAdministrator = function() {
	return(this.axioma() >= 4);
}

Pektis.prototype.oxiAdministrator = function() {
	return !this.isAdministrator();
}

Pektis.prototype.isProedros = function() {
	return(this.axioma() >= 5);
}

Pektis.prototype.oxiProedros = function() {
	return !this.isProedros();
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

Pektis.prototype.katastasi = function() {
	if (!this.peparam.hasOwnProperty('ΚΑΤΑΣΤΑΣΗ')) return 'A';

	switch (this.peparam['ΚΑΤΑΣΤΑΣΗ']) {
	case 'A':
	case 'B':
		return this.peparam['ΚΑΤΑΣΤΑΣΗ'];
	}

	globals.fatal('Pektis.katastasi: ' + this.peparam['ΚΑΤΑΣΤΑΣΗ'] + ': invalid internal value');
}

Pektis.prototype.isApasxolimenos = function() {
	return(this.katastasi() == 'B');
}

Pektis.prototype.isDiathesimos = function() {
	return(this.katastasi() == 'A');
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

Pektis.prototype.plati = function() {
	if (!this.peparam.hasOwnProperty('ΠΛΑΤΗ')) return 'B';

	switch (this.peparam['ΠΛΑΤΗ']) {
	case 'B':
	case 'R':
	case 'N':
		return this.peparam['ΠΛΑΤΗ'];
	}

	globals.fatal('Pektis.plati: ' + this.peparam['ΠΛΑΤΗ'] + ': invalid internal value');
}

Pektis.prototype.paraskinio = function() {
	if (!this.peparam.hasOwnProperty('ΠΑΡΑΣΚΗΝΙΟ')) return globals.defaultParaskinio;

	if (typeof this.peparam['ΠΑΡΑΣΚΗΝΙΚΟ'] == 'string') return this.peparam['ΠΑΡΑΣΚΗΝΙΟ'];

	globals.fatal('Pektis.paraskinio: ' + this.peparam['ΠΑΡΑΣΚΗΝΙΟ'] + ': invalid internal value');
}

Pektis.prototype.blockimage = function() {
	if (!this.peparam.hasOwnProperty('BLOCKIMAGE')) return false;
	if (this.peparam['BLOCKIMAGE'] === 1) return true;
	if (this.peparam['BLOCKIMAGE'] === 0) return false;
	globals.fatal('Pektis.blockimage: ' + this.peparam['BLOCKIMAGE'] + ': invalid internal value');
}

Pektis.prototype.letimage = function() {
	return !this.blockimage();
}

Pektis.prototype.movietime = function() {
	if (!this.peparam.hasOwnProperty('MOVIETIME')) return 'M';

	switch (this.peparam['MOVIETIME']) {
	case 'M':
	case 'R':
		return this.peparam['MOVIETIME'];
	}

	globals.fatal('Pektis.movietime: ' + this.peparam['MOVIETIME'] + ': invalid internal value');
}

Pektis.prototype.moviescale = function() {
	if (!this.peparam.hasOwnProperty('MOVIESCALE')) return globals.defaultMoviescale;

	var err = false;
	if (isNaN(this.peparam['MOVIESCALE'])) err = true;
	else if (parseInt(this.peparam['MOVIESCALE']) != this.peparam['MOVIESCALE']) err = true;
	else if (timi < 1) err = true;
	else return this.peparam['MOVIESCALE'];

	globals.fatal('Pektis.moviescale: ' + this.peparam['moviescale'] + ': invalid internal value');
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

Pektis.prototype.isAsoi = function() {
	if (!this.peparam.hasOwnProperty('ΑΣΟΙ')) return true;
	if (this.peparam['ΑΣΟΙ'] === true) return true;
	if (this.peparam['ΑΣΟΙ'] === false) return false;

	globals.fatal('Pektis.asoi: ' + this.peparam['ΑΣΟΙ'] + ': invalid internal value');
}

Pektis.prototype.isPaso = function() {
	if (!this.peparam.hasOwnProperty('ΠΑΣΟ')) return false;
	if (this.peparam['ΠΑΣΟ'] === true) return true;
	if (this.peparam['ΠΑΣΟ'] === false) return false;

	globals.fatal('Pektis.paso: ' + this.peparam['ΠΑΣΟ'] + ': invalid internal value');
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

Pektis.prototype.telioma = function() {
	if (!this.peparam.hasOwnProperty('ΤΕΛΕΙΩΜΑ')) return 'N';
	switch (this.peparam['ΤΕΛΕΙΩΜΑ']) {
	case 'N':
	case 'U':
	case 'F':
		return this.peparam['ΤΕΛΕΙΩΜΑ'];
	}

	globals.fatal('Pektis.telioma: ' + this.peparam['ΤΕΛΕΙΩΜΑ'] + ': invalid internal value');
}

Pektis.prototype.teliomaKanoniko = function() {
	return(this.telioma() == 'N');
}

Pektis.prototype.teliomaAnisoropo = function() {
	return(this.telioma() == 'U');
}

Pektis.prototype.teliomaDikeo = function() {
	return(this.telioma() == 'F');
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η μέθοδος "isSinedria" μας επιστρέφει τη συνεδρία τού παίκτη εφόσον
// υπάρχει. Αν δεν υπάρχει επιστρέφει undefined.

Pektis.prototype.isSinedria = function() {
	return Skiniko.isSinedria(this.login);
}

// Η μέθοδος "oxiSinedria" επιστρέφει true εφόσον ΔΕΝ υφίσταται συνεδρία
// για τον συγκεκριμένο παίκτη στο σκηνικό, αλλιώς επιστρέφει false.

Pektis.prototype.oxiSinedria = function() {
	return !this.isSinedria();
}

Pektis.prototype.isOnline = function() {
	return Skiniko.isSinedria(this.login);
}

Pektis.prototype.isOffline = function() {
	return !this.isSinedria();
}

// Η μέθοδος "isTrapezi" επιστρέφει ένα από τα τραπέζια στα οποία μετέχει
// ο παίκτης ως παίκτης. Η επιλογή του τραπεζιού γίνεται στην τύχη. Αν
// ο παίκτης δεν μετέχει σε κανένα τραπέζι ως παίκτης, τότε επιστρέφεται
// null.

Pektis.prototype.isTrapezi = function() {
	for (var i in Skiniko.trapezi) {
		for (var j in Prefadoros.thesiValid) {
			if (Skiniko.trapezi[i].pektis(j) == this.login) {
				return Skiniko.trapezi[i];
			}
		}
	}

	return null;
}

// Η μέθοδος "isApoklismenos" δέχεται ως παράμετρο το login name κάποιου παίκτη και
// επιστρέφει true αν ο παίκτης στον οποίον εφαρμόζεται η μέθοδος έχει αποκλείσει
// τον συγκεκριμένο παίκτη.

Pektis.prototype.isApoklismenos = function(login) {
	if (!this.fetchProp('sxesi').hasOwnProperty(login)) return false;
	if (!this.sxesi[login].hasOwnProperty('sxesi')) return false;
	return(this.sxesi[login]['sxesi'] == 'B');
}

// Η μέθοδος "isFilos" δέχεται ως παράμετρο το login name κάποιου παίκτη και
// επιστρέφει true αν ο παίκτης στον οποίον εφαρμόζεται η μέθοδος έχει φίλο
// τον συγκεκριμένο παίκτη.

Pektis.prototype.isFilos = function(login) {
	if (!this.fetchProp('sxesi').hasOwnProperty(login)) return false;
	if (!this.sxesi[login].hasOwnProperty('sxesi')) return false;
	return(this.sxesi[login]['sxesi'] == 'F');
}

// Η μέθοδος "oxiFilos" δέχεται ως παράμετρο το login name κάποιου παίκτη και
// επιστρέφει true αν ο παίκτης στον οποίον εφαρμόζεται η μέθοδος ΔΕΝ έχει φίλο
// τον συγκεκριμένο παίκτη.

Pektis.prototype.oxiFilos = function(login) {
	return !this.isFilos(login);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η κλάση "Sizitisi" απεικονίζει τις γραμμές συζήτησης, είτε πρόκειται για τη
// δημόσια συζήτηση που εξελίσσεται στο καφενείο, είτε πρόκειτα για συζήτηση
// τραπεζιού.

Sizitisi = function(sizitisi) {
	this.pote = globals.toraServer();
	this.sizitisiSet(sizitisi);
}

Sizitisi.prototype.sizitisiSet = function(sizitisi) {
	var i;
	for (i in sizitisi) {
		this[i] = sizitisi[i];
	}
	return this;
}

// Η μέθοδος "add" προσθέτει μια γραμμή συζήτησης στην μέχρι τώρα εξελιχθείσα
// συζήτηση.

Sizitisi.prototype.sizitisiAdd = function(trapezi) {
	this.checkProp();
	if (trapezi) trapezi.sizitisi.push(this);
	else Skiniko.sizitisi.push(this);
	return this;
}

Sizitisi.prototype.checkProp = function() {
	this.fetchProp('pektis');
	this.fetchProp('kimeno');
	if (!this.hasOwnProperty('pote')) this.pote = globals.toraServer();
	return this;
}

Sizitisi.prototype.fetchProp = function(prop) {
	if (this.hasOwnProperty(prop)) return this[prop];
	globals.fatal('Sizitisi.fetchProp: "' + prop + '" missing property');
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

Prosklisi = function(prosklisi) {
	for (var i in prosklisi) this[i] = prosklisi[i];
}

Prosklisi.prototype.fetchProp = function(prop) {
	if (this.hasOwnProperty(prop)) return this[prop];
	globals.fatal('Prosklisi.fetchProp: "' + prop + '" missing property');
}

// Η μέθοδος "add" προσθέτει μια πρόσκληση στο σκηνικό. Ουσιαστικά εισάγεται
// η πρόσκληση στη λίστα προσκλήσεων του τραπεζιού ΚΑΙ στη γενική λίστα
// προσκλήσεων.

Prosklisi.prototype.prosklisiAdd = function() {
	if (Skiniko.isProsklisi(this.fetchProp('kodikos')))
		globals.fatal('Prosklisi.add: υπάρχει ήδη η πρόσκληση στο σκηνικό');

	var trapezi = Skiniko.fetchTrapezi(this.fetchProp('trapezi'));
	if (trapezi.fetchProp('prosklisi').hasOwnProperty(this.kodikos)) globals.fatal('Prosklisi.add: ' +
		this.kodikos + ': υπάρχει ήδη η πρόσκληση στο τραπέζι');
	trapezi.trapeziEpafi();

	this.fetchProp('apo');
	if (!this.hasOwnProperty('pros')) this.pros = this.apo;
	if (!this.hasOwnProperty('epidosi')) this.epidosi = globals.toraServer();

	trapezi.prosklisi[this.kodikos] = this;
	Skiniko.prosklisi[this.kodikos] = this;
	return this;
}

// Η μέθοδος "del" διαγράφει μια πρόσκληση από το σκηνικό. Ουσιαστικά διαγράφεται
// η πρόσκληση από τη λίστα προσκλήσεων τού τραπεζιού ΚΑΙ από τη γενική λίστα
// προσκλήσεων.

Prosklisi.prototype.prosklisiDel = function() {
	if (Skiniko.oxiProsklisi(this.fetchProp('kodikos'))) globals.fatal('Prosklisi.del: ' + this.kodikos +
		': δεν βρέθηκε η πρόσκληση στο σκηνικό');

	var trapezi = Skiniko.fetchTrapezi(this.fetchProp('trapezi'));
	globals.fetchProp(trapezi.fetchProp('prosklisi'), this.kodikos);
	trapezi.trapeziEpafi();

	delete Skiniko.prosklisi[this.kodikos];
	delete trapezi.prosklisi[this.kodikos];
	return this;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

Sxesi = function(sxesi) {
	for (var i in sxesi) this[i] = sxesi[i];
}

Sxesi.prototype.fetchProp = function(prop) {
	if (this.hasOwnProperty(prop)) return this[prop];
	globals.fatal('Sxesi.fetchProp: "' + prop + '" missing property');
}

// Η μέθοδος "add" προσθέτει μια σχέση στο σκηνικό. Ουσιαστικά εισάγεται
// η σχέση στη λίστα σχέσεων του παίκτη.

Sxesi.prototype.sxesiAdd = function() {
	if (!this.hasOwnProperty('sxesi')) return this.sxesiDel();

	switch (this.fetchProp('sxesi')) {
	case 'F':
	case 'B':
		break;
	default:
		globals.fatal('Sxesi.add: ' + this.sxesi + ': invalid "sxesi" value');
	}

	if (!this.hasOwnProperty('dimiourgia')) this.dimiourgia = globals.toraServer();
	Skiniko.fetchPektis(this.fetchProp('pektis')).fetchProp('sxesi')[this.fetchProp('sxetizomenos')] = this;
	return this;
}

// Η μέθοδος "del" διαγράφει μια σχέση από το σκηνικό. Ουσιαστικά διαγράφεται
// η σχέση από τη λίστα σχέσεων τού παίκτη.

Sxesi.prototype.sxesiDel = function() {
	delete Skiniko.fetchPektis(this.fetchProp('pektis')).fetchProp('sxesi')[this.fetchProp('sxetizomenos')];
	return this;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η κλάση "Dianomi" απεικονίζει τις διανομές.

Dianomi = function(dianomi) {
	var i;

	this.enarxi = globals.toraServer();
	this.kasa1 = 0;
	this.metrita1 = 0;
	this.kasa2 = 0;
	this.metrita2 = 0;
	this.kasa3 = 0;
	this.metrita3 = 0;
	this.energia = [];

	for (i in dianomi) this[i] = dianomi[i];
}

Dianomi.prototype.fetchProp = function(prop) {
	if (this.hasOwnProperty(prop)) return this[prop];
	globals.fatal('Dianomi.fetchProp: "' + prop + '" missing property');
}

// Η μέθοδος "isEnergia" επιστρέφει true εφόσον το array ενεργειών τής διανομής
// περιέχει τουλάχιστον μια ενέργεια.

Dianomi.prototype.isEnergia = function() {
	return this.energia.length;
}

// Η μέθοδος "oxiEnergia" επιστρέφει true εφόσον η διανομή δεν περιέχει καμία
// ενέργεια.

Dianomi.prototype.oxiEnergia = function() {
	return !this.isEnergia();
}

// Η μέθοδος "telefteaEnergia" επιστρέφει την τελευταία ενέργεια τής διανομής.

Dianomi.prototype.telefteaEnergia = function() {
	if (this.oxiEnergia()) globals.fatal('Dianomi.telefteaEnergia: ' + this.kodikos + ': δεν υπάρχουν ενέργειες');
	return this.energia[this.energia.length - 1];
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Η κλάση "Energia" απεικονίζει τις ενέργειες που γίνονται σε κάθε διανομή.

Energia = function(energia) {
	var i;

	this.pote = globals.toraServer();
	for (i in energia) this[i] = energia[i];
}

Energia.prototype.fetchProp = function(prop) {
	if (this.hasOwnProperty(prop)) return this[prop];
	globals.fatal('Energia.fetchProp: "' + prop + '" missing property');
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

Profinfo = function(profinfo) {
	for (var i in profinfo) this[i] = profinfo[i];
}

Profinfo.prototype.fetchProp = function(prop) {
	if (this.hasOwnProperty(prop)) return this[prop];
	globals.fatal('Profinfo.fetchProp: "' + prop + '" missing property');
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////@

// Ακολουθεί η κλάση "Kinisi" η οποία περιγράφει τις επιμέρους κινήσεις.
// Μπορούμε προαιρετικά να περάσουμε ένα (literal) αντικείμενο κίνησης.

Kinisi = function(kinisi) {
	this.kinisiSet(kinisi);
}

Kinisi.prototype.kinisiSet = function(kinisi) {
	var i, t;

	for (i in kinisi) {
		switch (t = (typeof kinisi[i])) {
		case 'object':
			eval('this[i] = ' + JSON.stringify(kinisi[i]) + ';');
			break;
		case 'number':
		case 'string':
			this[i] = kinisi[i];
			break;
		default:
			globals.fatal('Kinisi: ' + t + ': unknown type');
		}
	}

	return this;
}

Kinisi.prototype.isProp = function(prop) {
	return this.hasOwnProperty(prop);
}

Kinisi.prototype.oxiProp = function(prop) {
	return !this.isProp(prop);
}

Kinisi.prototype.fetchProp = function(prop) {
	if (this.isProp(prop)) return this[prop];
	globals.fatal('Kinisi.fetchProp: "' + prop + '" missing property');
}
